﻿Option Strict On
Option Explicit On

Imports System.Drawing

Public NotInheritable Class Geometry
    Public Const ZERO As Single = 0.0F
    Public Const ONE As Single = 1.0F
    Public Const TWO As Single = 2.0F

    Public Const HALF_CIRCLE As Single = 180.0F
    Public Const FULL_CIRCLE As Single = 360.0F
    Public Const RIGHT_ANGLE As Single = 90.0F

    Public Const PI As Single = CSng(System.Math.PI)
    Public Const TWO_PI As Single = PI * TWO
    Public Const PI_SQUARED As Single = PI * PI

    Protected Sub New()
    End Sub

    Public Shared Function Atan(ByVal value As Single) As Single
        Return CSng(System.Math.Atan(CDbl(value)))
    End Function

    Public Shared Function Atan2(ByVal y As Single, ByVal x As Single) As Single
        Return CSng(System.Math.Atan2(CDbl(y), CDbl(x)))
    End Function

    Public Shared Function Between(ByVal value As Single, ByVal min As Single, ByVal max As Single) As Boolean
        Return value >= min AndAlso value <= max
    End Function

    Public Shared Function Between(ByVal value As Integer, ByVal min As Integer, ByVal max As Integer) As Boolean
        Return value >= min AndAlso value <= max
    End Function

    Public Shared Function Clamp(ByVal value As Single, ByVal min As Single, ByVal max As Single) As Single
        Return Geometry.Max(min, Geometry.Min(value, max))
    End Function

    Public Shared Function Clamp(ByVal value As Integer, ByVal min As Integer, ByVal max As Integer) As Integer
        Return CInt(Geometry.Max(min, Geometry.Min(value, max)))
    End Function

    Public Shared Function ConvertArrayPointFToVector2(ByVal source() As PointF) As Vector2()
        Return Array.ConvertAll(Of PointF, Vector2)(source, Function(p As PointF) New Vector2(p))
    End Function

    Public Shared Function ConvertArrayVector2ToPointF(ByVal source() As Vector2) As PointF()
        Return Array.ConvertAll(Of Vector2, PointF)(source, Function(v As Vector2) v.ToPointF)
    End Function

    Public Shared Function Cos(ByVal radians As Single) As Single
        Return CSng(System.Math.Cos(CDbl(radians)))
    End Function

    Public Shared Function DegreesToRadians(ByVal degrees As Single) As Single
        Return degrees * (PI / HALF_CIRCLE)
    End Function

    Public Shared Function EqualWithin(ByVal source As Single, ByVal target As Single, ByVal tolerance As Single) As Boolean
        Return source - target <= tolerance
    End Function

    Public Shared Function EqualWithin(ByVal source As Integer, ByVal target As Integer, ByVal tolerance As Integer) As Boolean
        Return source - target <= tolerance
    End Function

    Public Shared Function GetAngleBetween(a As Vector2, b As Vector2) As Single
        Dim c As New Vector2(a.X, b.Y)
        Return CSng(Math.Atan2(GetCrossProductLength(a, b, c), GetDotProduct(a, b, c)))
    End Function

    Public Shared Function GetAngleBetween(a As Vector2, b As Vector2, c As Vector2) As Single
        Return CSng(Math.Atan2(GetCrossProductLength(a, b, c), GetDotProduct(a, b, c)))
    End Function

    Public Shared Function GetCollisionVelocity(ByVal velocityOne As Vector2, ByVal massOne As Single, ByVal velocityTwo As Vector2, ByVal massTwo As Single) As Vector2
        Dim x As Single = (massOne * velocityOne.X + massTwo * velocityTwo.X) / (massOne + massTwo)
        Dim y As Single = (massOne * velocityOne.Y + massTwo * velocityTwo.Y) / (massOne + massTwo)
        Return New Vector2(x, y)
    End Function

    ' Return the dot product AB · BC.
    ' Note that AB · BC = |AB| * |BC| * Cos(theta).
    Public Shared Function GetDotProduct(a As Vector2, b As Vector2, c As Vector2) As Single
        Return (a.X - b.X) * (c.X - b.X) + (a.Y - b.Y) * (c.Y - b.Y)
    End Function

    ' The cross product is a vector perpendicular to AB
    ' and BC having length |AB| * |BC| * Sin(theta) and
    ' with direction given by the right-hand rule.
    ' For two vectors in the X-Y plane, the result is a
    ' vector with X and Y components 0 so the Z component
    ' gives the vector's length and direction.
    Public Shared Function GetCrossProductLength(a As Vector2, b As Vector2, c As Vector2) As Single
        Return (a.X - b.X) * (c.Y - b.Y) - (a.Y - b.Y) * (c.X - b.X)
    End Function

    Public Shared Function GetPolygonPoints(ByVal radius As Single, ByVal sides As Integer, ByVal position As Vector2) As Vector2()
        Dim points(sides - 1) As Vector2
        For side As Integer = 0 To sides - 1
            Dim delta As Single = PI / sides + side * 2 * PI / sides
            points(side) = New Vector2(position.X + radius * CSng(System.Math.Sin(delta)), position.Y + radius * CSng(System.Math.Cos(delta)))
        Next
        Return points
    End Function

    Public Shared Function GetPolygonPoints(ByVal radius As Single, ByVal sides As Integer, ByVal position As PointF) As PointF()
        Dim points(sides - 1) As PointF
        For side As Integer = 0 To sides - 1
            Dim delta As Single = PI / sides + side * 2 * PI / sides
            points(side) = New PointF(position.X + radius * CSng(System.Math.Sin(delta)), position.Y + radius * CSng(System.Math.Cos(delta)))
        Next
        Return points
    End Function

    Public Shared Function InRangeOf(source As Single, target As Single, range As Single) As Boolean
        Return System.Math.Abs(source - target) <= range
    End Function

    Public Shared Function InRangeOf(source As Integer, target As Integer, range As Integer) As Boolean
        Return System.Math.Abs(source - target) <= range
    End Function

    Public Shared Function InRangeOf(source As Double, target As Double, range As Double) As Boolean
        Return System.Math.Abs(source - target) <= range
    End Function

    Public Shared Function InRangeOf(source As Decimal, target As Decimal, range As Decimal) As Boolean
        Return System.Math.Abs(source - target) <= range
    End Function

    Public Shared Function InRangeOf(source As Vector2, target As Vector2, range As Decimal) As Boolean
        Return source.DistanceTo(target) <= range
    End Function

    Public Shared Function IsInfinite(ByVal value As Single) As Boolean
        Return Single.IsInfinity(value)
    End Function

    Public Shared Function IsInfinite(ByVal value As Vector2) As Boolean
        Return Single.IsInfinity(value.X) OrElse Single.IsInfinity(value.Y)
    End Function

    Public Shared Function IsRealNumber(ByVal value As Single) As Boolean
        If Single.IsNaN(value) Then Return False
        Return Not Single.IsInfinity(value)
    End Function

    Public Shared Function Max(ByVal value1 As Single, ByVal value2 As Single) As Single
        If value2 > value1 Then Return value2
        Return value1
    End Function

    Public Shared Function Min(ByVal value1 As Single, ByVal value2 As Single) As Single
        If value2 < value1 Then Return value2
        Return value1
    End Function

    Public Shared Function Max(ByVal value1 As Integer, ByVal value2 As Integer) As Integer
        If value2 > value1 Then Return value2
        Return value1
    End Function

    Public Shared Function Min(ByVal value1 As Integer, ByVal value2 As Integer) As Integer
        If value2 < value1 Then Return value2
        Return value1
    End Function

    ' Return True if the point is in the polygon.
    ' ORIGINALLY BY Tommytwotrain: http://social.msdn.microsoft.com/Forums/en-US/bae18a08-0f05-4dfb-bcf4-70ce7cb9b34f/algorithm-to-detect-whether-a-specific-point-is-within-the-area-of-a-triangle?forum=vbgeneral#366621b2-67b9-4ea0-9f1c-7c17838a81c3
    Public Shared Function PolygonContains(ByVal target As Vector2, ByVal polygon() As Vector2) As Boolean
        ' Get the angle between the point and the first and last vertices.
        Dim total_angle As Single = GetAngleBetween(polygon(polygon.Length - 1), target, polygon(0))
        ' Add the angles from the point to each other pair of vertices.
        For pt As Integer = 0 To polygon.Length - 2
            total_angle = total_angle + GetAngleBetween(polygon(pt), target, polygon(pt + 1))
        Next
        ' The total angle should be 2 * PI or -2 * PI if the point is in the polygon and close to zero
        ' if the point is outside the polygon.
        Return Math.Abs(total_angle) > Math.PI
    End Function

    Public Shared Function PolygonContains(ByVal target As PointF, ByVal polygon() As PointF) As Boolean
        ' Get the angle between the point and the first and last vertices.
        Dim total_angle As Single = GetAngleBetween(polygon(polygon.Length - 1), target, polygon(0))
        ' Add the angles from the point to each other pair of vertices.
        For pt As Integer = 0 To polygon.Length - 2
            total_angle = total_angle + GetAngleBetween(polygon(pt), target, polygon(pt + 1))
        Next
        ' The total angle should be 2 * PI or -2 * PI if the point is in the polygon and close to zero
        ' if the point is outside the polygon.
        Return Math.Abs(total_angle) > Math.PI
    End Function

    Public Shared Function Pow(ByVal value As Single, ByVal exponent As Integer) As Single
        For count As Integer = 0 To exponent - 2
            value *= value
        Next
        Return value
    End Function

    Public Shared Function RadiansToDegrees(ByVal radians As Single) As Single
        Return CSng(radians * (HALF_CIRCLE / PI))
    End Function

    Public Shared Function ReflectAngle(initialAngleInDegrees As Single, normalAngle As Single) As Single
        Return WrapDegrees(2 * normalAngle - initialAngleInDegrees)
    End Function

    Public Shared Function ReflectHorizontal(initialAngleInDegrees As Single) As Single
        Return ReflectAngle(initialAngleInDegrees, 180)
    End Function

    Public Shared Function ReflectVertical(initialAngleInDegrees As Single) As Single
        Return ReflectAngle(initialAngleInDegrees, 90)
    End Function

    Public Shared Function Sin(ByVal radians As Single) As Single
        Return CSng(System.Math.Sin(CDbl(radians)))
    End Function

    Public Shared Function Sqrt(ByVal value As Single) As Single
        Return CSng(CDbl(value) ^ 0.5)
    End Function

    Public Shared Function Square(ByVal value As Single) As Single
        Return value * value
    End Function

    Public Shared Sub Swap(ByRef value1 As Single, ByRef value2 As Single)
        Dim temp As Single = value2
        value1 = value2
        value2 = temp
    End Sub

    Public Shared Sub Swap(ByRef value1 As Integer, ByRef value2 As Integer)
        Dim temp As Integer = value2
        value1 = value2
        value2 = temp
    End Sub

    Public Shared Function Tan(ByVal value As Single) As Single
        Return CSng(System.Math.Tan(CDbl(value)))
    End Function

    Public Shared Function TriangleContains(target As Vector2, triangleA As Vector2, triangleB As Vector2, triangleC As Vector2) As Boolean
        Dim s As Single = triangleA.Y * triangleC.X - triangleA.X * triangleC.Y + (triangleC.Y - triangleA.Y) * target.X + (triangleA.X - triangleC.X) * target.Y
        Dim t As Single = triangleA.X * triangleB.Y - triangleA.Y * triangleB.X + (triangleA.Y - triangleB.Y) * target.X + (triangleB.X - triangleA.X) * target.Y
        If Not ((s < 0) = (t < 0)) Then Return False
        Dim A As Single = -triangleB.Y * triangleC.X + triangleA.Y * (triangleC.X - triangleB.X) + triangleA.X * (triangleB.Y - triangleC.Y) + triangleB.X * triangleC.Y
        If (A < 0) Then s = -s : t = -t : A = -A
        Return s > 0 AndAlso t > 0 AndAlso (s + t) < A
    End Function

    Public Shared Function TriangleContains(target As PointF, triangleA As PointF, triangleB As PointF, triangleC As PointF) As Boolean
        Dim s As Single = triangleA.Y * triangleC.X - triangleA.X * triangleC.Y + (triangleC.Y - triangleA.Y) * target.X + (triangleA.X - triangleC.X) * target.Y
        Dim t As Single = triangleA.X * triangleB.Y - triangleA.Y * triangleB.X + (triangleA.Y - triangleB.Y) * target.X + (triangleB.X - triangleA.X) * target.Y
        If Not ((s < 0) = (t < 0)) Then Return False
        Dim A As Single = -triangleB.Y * triangleC.X + triangleA.Y * (triangleC.X - triangleB.X) + triangleA.X * (triangleB.Y - triangleC.Y) + triangleB.X * triangleC.Y
        If (A < 0) Then s = -s : t = -t : A = -A
        Return s > 0 AndAlso t > 0 AndAlso (s + t) < A
    End Function

    Public Shared Function WrapAngle(ByVal angle As Single, unit As AngleUnit) As Single
        If unit = AngleUnit.Degrees Then
            Return WrapDegrees(angle)
        Else
            Return WrapRadians(angle)
        End If
    End Function

    Public Shared Function WrapDegrees(degrees As Single) As Single
        Return RadiansToDegrees(WrapRadians(DegreesToRadians(degrees)))
    End Function

    Public Shared Function WrapRadians(ByVal radians As Single) As Single
        While radians < -PI
            radians += TWO_PI
        End While
        While radians > PI
            radians -= TWO_PI
        End While
        Return radians
    End Function
End Class


